home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Gigarom 1
/
Gigarom Macintosh Archives (Quantum Leap)(CDRM1080320)(1993).iso
/
FILES
/
DEV
/
I-Z
/
TransSkel.cpt
/
TransEdit.pas
< prev
next >
Wrap
Pascal/Delphi Source File
|
1987-02-25
|
41KB
|
1,561 lines
{ TransEdit.c version 1.0 - TransSkel plug-in module supporting an}
{ arbitrary number of generic edit windows. Each window may be}
{ bound to a file.}
{ *** Requires FakeAlert.pas for proper linking! ***}
{ Shortcomings:}
{ Doesn't check for the obvious out of memory conditions.}
{ TransSkel and TransEdit are public domain, and are written by:}
{ Paul DuBois}
{ Wisconsin Regional Primate Research Center}
{ 1220 Capital Court}
{ Madison WI 53706 USA}
{ UUCP: {allegra,ihnp4,seismo}
{ The Pascal Version of TransSkel is public domain and was ported by }
{ Owen Hartnett }
{ Ωhm Software }
{ 163 Richard Drive }
{ Tiverton, RI 02878 }
{ CSNET: omh@cs.brown.edu.CSNET }
{ ARPA: omh%cs.brown.edu@relay.cs.net-relay.ARPA }
{ UUCP: [ihnp4,allegra]!brunix !omh }
{ Unlike the C version of TransEdit, this version does not support}
{ a single window mode. Because there are no conditional compile directives in}
{ LightSpeed Pascal, this was not possible}
UNIT TransEditpas;
INTERFACE
USES
FakeAlert, TransSkelPas;
TYPE
SFReplyPtr = ^SFReply;
FUNCTION EWindowClose (theWind : WindowPtr) : boolean;
FUNCTION IsEWindow (theWind : WindowPtr) : Boolean;
FUNCTION IsEWindowDirty (theWind : WindowPtr) : Boolean;
FUNCTION GetEWindowTE (theWind : WindowPtr) : TEHandle;
FUNCTION GetEWindowFile (theWind : WindowPtr;
fileInfo : SFReplyPtr) : Boolean;
PROCEDURE SetEWindowProcs (theWind : WindowPtr;
pKey, pActivate, pClose : ProcPtr);
PROCEDURE SetEWindowStyle (theWind : WindowPtr;
font, size, wrap, just : integer);
PROCEDURE EWindowOverhaul (theWind : WindowPtr;
showCaret, recalc, dirty : Boolean);
PROCEDURE EWindowEditOp (item : integer);
PROCEDURE SetEWindowCreator (creat : OSType);
FUNCTION EWindowSave (theWind : WindowPtr) : Boolean;
FUNCTION EWindowSaveAs (theWind : WindowPtr) : Boolean;
FUNCTION EWindowSaveCopy (theWind : WindowPtr) : Boolean;
FUNCTION EWindowRevert (theWind : WindowPtr) : Boolean;
FUNCTION NewEWindow (bounds : Rect;
title : Str255;
visible : Boolean;
behind : WindowPtr;
goAway : Boolean;
refNum : longint;
bindToFile : Boolean) : WindowPtr;
FUNCTION ClobberEWindows : Boolean;
PROCEDURE TransEditInit;
IMPLEMENTATION
CONST
{ Edit window types, constants, variables.}
enter = 3;
cr = 13;
monaco = 4;
shiftKey = $200;
{ Edit menu item numbers }
undo = 1;
cut = 3;
copy = 4;
paste = 5;
clear = 6; { (it's ok if the host doesn't have this item) }
{ ewList points to a list of structures describing the known edit}
{ windows.}
TYPE
EIptr = ^EditInfoRec;
EIHandle = ^EIPtr;
EditInfoRec = RECORD
editWind : WindowPtr;
bound : Boolean;
editFile : SFReply;
editTE : TEHandle;
dirty : Boolean;
scroll : ControlHandle;
visLines : integer;
eKey, eActivate, eClose : ProcPtr;
eNext : EIHandle;
END;
VAR
e_font, e_size, e_wrap, e_just : integer;
e_key, e_activate, e_close : ProcPtr;
ewList : EIHandle;
{ Global variables - most of these are always synced to}
{ the current window. Note that not all these are set by}
{ SyncGlobals, since some are not often needed. When they}
{ are all needed, use SyncAllGlobals.}
editInfo : EIHandle; { window's info structure }
editWind : WindowPtr; { the window }
editTE : TEHandle; { window text }
editScroll : ControlHandle; { the scroll bar }
editFile : SFReply; { file information }
visLines : integer; { number of lines in window }
bound, dirty : Boolean; { true if window bound to file }
{ whether window is dirty }
eKey, eActivate, eClose : ProcPtr; { key click notifier }
{ activate event notifier }
{ close notifier }
windID : integer;
dlogWhere : Point; { GetFile/PutFile location }
creator : OSType; { default file creator }
clipRgn : RgnHandle;
PROCEDURE TransEditInit;
{Extra routine to do initialization of variables, LSP can't do this }
BEGIN
{ Default values for edit window text display characteristics}
{ and event notification procedures}
e_font := monaco; { default font }
e_size := 9; { default pointsize }
e_wrap := 0; { default word wrap (on) }
e_just := teJustLeft;{ default justification }
e_key := NIL; { default key procedure }
e_activate := NIL; { default activation procedure }
e_close := NIL; { default close procedure }
ewList := NIL;
editWind := NIL;
windID := 0;
dlogWhere.v := 70;
dlogWhere.h := 100;
creator := 'TEDT';
END;
{ -------------------------------------------------------------------- }
{ Miscellaneous Internal (private) Routines }
{ -------------------------------------------------------------------- }
{ Save and restore the current window's clip region}
PROCEDURE SaveClipRgn;
BEGIN
clipRgn := NewRgn;
GetClip(clipRgn);
END;
PROCEDURE RestoreClipRgn;
BEGIN
SetClip(clipRgn);
DisposeRgn(clipRgn);
END;
{ Draw grow box in lower right hand corner of window.}
PROCEDURE DrawGrowBox;
VAR
r : Rect;
BEGIN
SaveClipRgn;
r := editWind^.portRect;
r.left := r.right - 15;
r.top := r.bottom - 15; { draw only in corner }
ClipRect(r);
DrawGrowIcon(editWind);
RestoreClipRgn;
END;
{ -------------------------------------------------------------------- }
{ Lowest-level Internal (Private) Edit Window Routines }
{ -------------------------------------------------------------------- }
{ Get edit window info associated with window.}
{ Return nil if window isn't a known edit window.}
FUNCTION GetEInfo (theWind : WindowPtr) : EIHandle;
VAR
h : EIHandle;
foundflag : Boolean;
BEGIN
h := ewList;
foundflag := false; { set to true when window found !}
WHILE h <> NIL DO
BEGIN
IF h^^.editWind = theWind THEN
BEGIN
GetEInfo := h;
h := NIL;
foundflag := true;
END
ELSE
h := h^^.eNext;
END;
IF foundflag = false THEN
GetEInfo := NIL;
END;
{ Synchronize globals to an edit window and make it the}
{ current port. theWind must be a legal edit window, with one}
{ exception: if theWind is nil, the variables are synced to the}
{ port that's already current. That is safe (and correct) because:}
{ (i) nil is only passed by edit window handler procedures,}
{ which are only attached to edit windows}
{ (ii) TransSkel always sets the port to the window before}
{ calling the handler proc.}
{ Hence, using the current port under these circumstances always}
{ produces a legal edit window.}
PROCEDURE SyncGlobals (theWind : WindowPtr);
BEGIN
IF theWind = NIL THEN { use current window }
GetPort(theWind);
SetPort(theWind);
editWind := theWind;
editInfo := GetEInfo(editWind);
editTE := editInfo^^.editTE;
editScroll := editInfo^^.scroll;
visLines := editInfo^^.visLines;
END;
PROCEDURE SyncAllGlobals (theWind : WindowPtr);
BEGIN
SyncGlobals(theWind); { sync display globals }
editFile := editInfo^^.editFile;
bound := editInfo^^.bound; { procedure globals }
dirty := editInfo^^.dirty;
eKey := editInfo^^.eKey;
eActivate := editInfo^^.eActivate;
eClose := editInfo^^.eClose;
END;
{ Set dirty flag for current window}
PROCEDURE SetDirty (boolVal : Boolean);
BEGIN
editInfo^^.dirty := boolVal;
END;
{ -------------------------------------------------------------------- }
{ Internal (private) Display Routines }
{ -------------------------------------------------------------------- }
{ Calculate the dimensions of the editing rectangle for}
{ editWind (which must be set properly and is assumed to be}
{ the current port). (The viewRect and destRect are the}
{ same size.) Assumes the port, text font and text size are all}
{ set properly. The viewRect is sized so that an integral}
{ number of lines can be displayed in it, i.e., so that a}
{ partial line never shows at the bottom. If that's not}
{ done, funny things can happen to the caret.}
PROCEDURE GetEditRect (VAR r : Rect);
VAR
f : FontInfo;
lineHeight : integer;
BEGIN
GetFontInfo(f);
lineHeight := f.ascent + f.descent + f.leading;
r := editWind^.portRect;
r.left := r.left + 4;
r.right := r.right - 17; { leave room for scroll bar }
r.top := r.top + 2;
r.bottom := r.top + ((r.bottom - r.top - 2) DIV lineHeight) * lineHeight;
END;
{ Set the edit rect properly.}
PROCEDURE SetEditRect;
VAR
r : Rect;
BEGIN
GetEditRect(r);
editTE^^.destRect.right := r.right;
editTE^^.viewRect := r;
END;
{ Calculate the dimensions of the scroll bar rectangle for}
{ editWind (which must be set properly). Make sure that}
{ the edges overlap the window frame and the grow box.}
PROCEDURE CalcScrollRect (VAR r : Rect);
BEGIN
r := editWind^.portRect;
r.right := r.right + 1;
r.top := r.top - 1;
r.left := r.right - 16;
r.bottom := r.bottom - 14;
END;
{ Return true if the mouse is in the non-scrollbar part of the}
{ edit window.}
FUNCTION PtInText (pt : Point) : Boolean;
VAR
r : Rect;
BEGIN
r := editWind^.portrect;
r.right := r.right - 15;
PtInText := PtInRect(pt, r);
END;
{ Set the cursor appropriately. If theCursor == iBeamCursor, check}
{ that it's really in the text area of an edit window (and if not}
{ set the cursor to an arrow instead). Otherwise, set the cursor}
{ to the given type (usually a watch).}
{ If the cursor is supposed to be set to an i-beam, it is assumed}
{ that the globals are synced, because DoCursor changes them and}
{ syncs them back.}
{ Pass -1 for theCursor to set the cursor to the arrow.}
PROCEDURE DoCursor (theCursor : integer);
VAR
pt : Point;
savePort : GrafPtr;
myCursor : CursHandle;
BEGIN
IF theCursor = iBeamCursor THEN { check whether there's an edit }
BEGIN { window in front and if so, }
theCursor := -1; { whether the cursor's in its }
IF (IsEWindow(FrontWindow)) THEN { text area }
BEGIN
GetPort(savePort);
SyncGlobals(FrontWindow);
GetMouse(pt);
IF (PtInText(pt)) THEN
theCursor := iBeamCursor;
SyncGlobals(savePort);
END;
END;
IF theCursor = -1 THEN
SetCursor(arrow)
ELSE
BEGIN
myCursor := GetCursor(theCursor);
SetCursor(myCursor^^);
END;
END;
{ Calculate the number of lines currently scrolled off}
{ the top.}
FUNCTION LinesOffTop : integer;
VAR
ePtr : TEPtr;
BEGIN
ePtr := editTE^;
LinesOffTop := ((ePtr^.viewRect.top - ePtr^.destRect.top) DIV ePtr^.lineHeight);
END;
{ Return the line number that the caret (or the beginning of}
{ the currently selected text) is in. Value returned is in}
{ the range 0..(**editTE).nLines. If = (**editTE).nLines, the}
{ caret is past the last line. The only special case to watch out}
{{ for is when the caret is at the very end of the text. If the}
{ last character is not a carriage return, then the caret is on}
{{ the (nLines-1)th line, not the (nLines)th line.}
{{{ (This really should do a binary search for speed.){}
FUNCTION LineWithCaret : integer;
VAR
i, nLines, teLength, selStart, lineStart : integer;
doneflag : Boolean;
mychars : CharsHandle;
BEGIN
selStart := editTE^^.selStart;
nLines := editTE^^.nLines;
teLength := editTE^^.teLength;
IF (selStart = teLength) THEN
BEGIN
mychars := TEGetText(editTE);
IF (teLength <> 0) AND (mychars^^[teLength - 1] <> char(cr)) THEN
LineWithCaret := nLines - 1
ELSE
LineWithCaret := nLines
END
ELSE
BEGIN
i := 0;
doneflag := false; { Not done yet! }
WHILE NOT doneflag DO
BEGIN
lineStart := editTE^^.lineStarts[i];
IF lineStart >= selStart THEN
BEGIN
IF lineStart <> selStart THEN
i := i - 1;
LineWithCaret := i;
doneflag := true;
END;
i := i + 1;
END;
END;
END;
{ Return the number of the last displayable line. That's one}
{ more than nLines if the text is empty or the last character}
{ is a carriage return.}
FUNCTION LastLine : integer;
VAR
nLines, teLength : integer;
mychars : CharsHandle;
BEGIN
nLines := editTE^^.nLines;
teLength := editTE^^.teLength;
myChars := TEGetText(editTE);
IF (mychars^^[teLength - 1] = char(cr)) OR (teLength = 0) THEN
nLines := nLines + 1;
LastLine := nLines;
END;
{ Set the maximum value of the scroll bar. }
PROCEDURE SetScrollMax;
VAR
topLines, scrollableLines, max : integer;
BEGIN
topLines := LinesOffTop;
scrollableLines := LastLine - visLines;
IF topLines > scrollableLines THEN
max := topLines
ELSE
max := scrollableLines;
IF max < 0 THEN
max := 0;
IF max <> GetCtlMax(editScroll) THEN
BEGIN
SetCtlMax(editScroll, max);
IF max > 0 THEN
HiliteControl(editScroll, 0)
ELSE
HiliteControl(editScroll, 255);
END;
END;
{ Set scroll bar current value (but only if it's different than}
{ the current value, to avoid needless flashing).}
PROCEDURE SetScrollValue (value : integer);
BEGIN
IF GetCtlValue(editScroll) <> value THEN
SetCtlValue(editScroll, value);
END;
{ Scroll to the correct position. lDelta is the}
{ amount to CHANGE the current scroll setting by.}
PROCEDURE ScrollText (lDelta : integer);
VAR
topVisLine, newTopVisLine : integer;
BEGIN
topVisLine := LinesOffTop;
newTopVisLine := topVisLine + lDelta;
IF newTopVisLine < 0 THEN { clip to range }
newTopVisLine := 0;
IF (newTopVisline > GetCtlMax(editScroll)) THEN
newTopVisLine := GetCtlMax(editScroll);
SetScrollValue(newTopVisLine);
TEScroll(0, (topVisLine - newTopVisLine) * editTE^^.lineHeight, editTE);
END;
{ Scroll to home position without redrawing.{}
PROCEDURE ScrollToHome;
VAR
r : Rect;
BEGIN
r := editTE^^.destRect;
OffsetRect(r, 0, 2 - r.top);
editTE^^.destRect := r;
END;
{ ClikLoop proc for autoscrolling text when the mouse is dragged out}
{ of the text view rectangle.}
{ The clipping region has to be set to include the scroll bar,}
{ because whenever this proc is called, TE has the region set down}
{ to the view rectangle - if it's not reset, changes to the scroll}
{ bar will not show up!}
FUNCTION AutoScroll : Boolean;
VAR
p : Point;
r : Rect;
BEGIN
SaveClipRgn;
ClipRect(editWind^.portRect);
GetMouse(p);
r := editTE^^.viewRect;
IF (p.v < r.top) THEN
ScrollText(-1)
ELSE IF (p.v > r.bottom) THEN
ScrollText(1);
RestoreClipRgn;
AutoScroll := true;
END;
{ Filter proc for tracking mousedown in scroll bar. The code for}
{ the part originally hit is shoved into the control's reference}
{ value by Mouse() before this is called.}
{ I suspect odd scrolling may occur for hits in paging regions if}
{ the window is allowed to size such that less than two lines show.}
PROCEDURE TrackScroll (theScroll : ControlHandle;
partCode : integer);
VAR
lDelta : integer;
BEGIN
IF partCode = GetCRefCon(theScroll) THEN { still in same part? }
BEGIN
CASE partCode OF
inUpButton :
lDelta := -1;
inDownButton :
lDelta := 1;
inPageUp :
lDelta := -(visLines - 1);
inPageDown :
lDelta := visLines - 1;
OTHERWISE
END;
ScrollText(lDelta);
END;
END;
{ Set the scroll bar properly and adjust the text in the}
{ window so that the line containing the caret is visible.}
{ If the line with the caret if more than a line outside of}
{ the viewRect, try to place it in the middle of the window.}
{}
{ Yes, it is necessary to SetScrollMax at the end.}
PROCEDURE AdjustDisplay;
VAR
caretLine, topVisLine, d : integer;
BEGIN
SetScrollMax;
caretLine := LineWithCaret;
topVisLine := LinesOffTop;
d := caretLine - topVisLine;
IF d < 0 THEN
IF d = -1 THEN
ScrollText(-1)
ELSE
ScrollText(d - (visLines DIV 2))
ELSE
BEGIN
d := caretLine - (topVisLine + visLines - 1);
IF d > 0 THEN
IF d = 1 THEN
ScrollText(1)
ELSE
ScrollText(d + (visLines DIV 2))
ELSE
SetScrollValue(topVisLine);
END;
SetScrollMax; { might have changed from scrolling }
END;
{ Overhaul the entire display. This is called for major}
{ catastrophes, such as resizing the window, or changes to}
{ the word wrap style. It makes sure the view and}
{ destination rectangles are sized properly, and that the bottom}
{ line of text never scrolls up past the bottom line of the}
{ window, if there's enough to fill the window, and that the}
{ scroll bar max and current values are set properly.}
{ Resizing the dest rect just means resetting the right edge}
{ (the top is NOT reset), since text might be scrolled off the}
{ top (i.e., destRect.top != 0).}
{ Doesn't redraw the control, though!}
PROCEDURE OverhaulDisplay (showCaret : Boolean;
recalc : Boolean);
VAR
r : Rect;
BEGIN
r := editTE^^.viewRect;
EraseRect(r); { erase current viewRect }
SetEditRect; { recalculate editing rects }
IF recalc THEN { recalculate line starts }
TECalText(editTE);
visLines := (editTE^^.viewRect.bottom - editTE^^.viewRect.top) DIV editTE^^.lineHeight;
editInfo^^.visLines := visLines;
IF showCaret THEN
AdjustDisplay
ELSE
SetScrollMax;
r := editTE^^.viewRect;
TEUpdate(r, editTE);
END;
{ ---------------------------------------------------------------- }
{ Window Handler Routines }
{ ---------------------------------------------------------------- }
{}
{ Handle mouse clicks in window. The viewRect is never tested}
{ directly, because if it were, clicks along the top, left and}
{ bottom edges of the window wouldn't register.}
PROCEDURE Mouse (thePt : Point;
t : longint;
mods : integer);
VAR
thePart, oldCtlValue, ignore : integer;
BEGIN
SyncGlobals(NIL); { sync to current port }
thePart := TestControl(editScroll, thePt);
IF thePart = inThumb THEN
BEGIN
oldCtlValue := GetCtlValue(editScroll);
IF TrackControl(editScroll, thePt, NIL) = inThumb THEN
ScrollText(GetCtlValue(editScroll) - oldCtlValue)
END
ELSE IF thePart <> 0 THEN
BEGIN
SetCRefCon(editScroll, longint(thePart));
ignore := TrackControl(editScroll, thePt, @TrackScroll);
END
ELSE IF (PtInText(thePt)) THEN
TEClick(thePt, BitAnd(mods, shiftKey) <> 0, editTE);
SetScrollMax;
END;
PROCEDURE callpnoarg (myProc : ProcPtr);
{ For all the Procedures that are called with no arguments }
INLINE
$205f, {movea.l (a7)+,a0 ; (a0) is a ptr to string, 4(a0) is mode}
$4e90;
PROCEDURE callpBoolean (myBool : Boolean;
myProc : ProcPtr);
{ Two calls use Booleans as one parameter arguments. This procedure handles }
{ both of them. }
INLINE
$205f, {movea.l (a7)+,a0 ; (a0) is a ptr to string, 4(a0) is mode}
$4e90;
{ Handle key clicks in window}
PROCEDURE Key (c : char;
mods : integer);
BEGIN
SyncAllGlobals(NIL); { sync to current port }
IF c <> char(enter) THEN
TEKey(c, editTE);
AdjustDisplay;
SetDirty(true);
IF eKey <> NIL THEN { report event to the host }
callpnoarg(eKey);
END;
{ When the window comes active, highlight the scroll bar appropriately.}
{ When the window is deactivated, un-highlight the scroll bar.}
{ Redraw the grow box in any case. Set the cursor (DoCursor avoids}
{ changing it from an ibeam to an arrow back to an ibeam, in the case}
{ where one edit window is going inactive and another is coming}
{ active).}
{}
{ Report the event to the host.}
PROCEDURE Activate (active : Boolean);
BEGIN
SyncAllGlobals(NIL); { sync to current port }
DrawGrowBox;
IF active THEN
BEGIN
TEActivate(editTE);
IF GetCtlMax(editScroll) > 0 THEN
HiliteControl(editScroll, 0)
ELSE
HiliteControl(editScroll, 255);
END
ELSE
BEGIN
TEDeactivate(editTE);
HiliteControl(editScroll, 255);
END;
DoCursor(iBeamCursor);
IF (eActivate <> NIL) THEN { report event to the host }
callpBoolean(active, eActivate);
END;
{ Close box was clicked. If user specified notify proc, call it.}
{ Otherwise do default close operation (ask about saving if dirty,}
{ etc.).}
PROCEDURE Close;
VAR
ignore : integer;
BEGIN
SyncAllGlobals(NIL); { sync to current port }
IF eclose <> NIL THEN
callpnoarg(eClose)
ELSE
ignore := integer(EWindowClose(editWind));
END;
{ Update window. The update event might be in response to a}
{ window resizing. If so, move and resize the scroll bar.}
{ The ValidRect call is done because the HideControl adds the}
{ control bounds box to the update region - which would generate}
{ another update event! Since everything gets redrawn below,}
{ the ValidRect is used to cancel the update.}
PROCEDURE UpDate (resized : Boolean);
VAR
r : Rect;
BEGIN
SyncGlobals(NIL); { sync to current port }
r := editWind^.portRect;
EraseRect(r);
IF resized THEN
BEGIN
HideControl(editScroll);
r := editScroll^^.contrlRect;
ValidRect(r);
CalcScrollRect(r);
SizeControl(editScroll, 16, r.bottom - r.top);
MoveControl(editScroll, r.left, r.top);
OverhaulDisplay(false, editTE^^.crOnly >= 0);
ShowControl(editScroll);
END
ELSE
BEGIN
OverhaulDisplay(false, false);
DrawControls(editWind);
END;
DrawGrowBox;
END;
{ Remove the edit window from the list, and dispose of it.}
{ This is called by SkelRmveWind, not directly by user program.}
{}
{ At this point it's too late to back out if any changes have been}
{ made to the text.}
{ Since the clobber procedure is never called except for real edit}
{ windows, and since the list must therefore be non-empty, it is}
{ not necessary to check the legality of the window or that the}
{ window's in the list.}
PROCEDURE Clobber;
VAR
h, h2 : EIHandle;
BEGIN
SyncGlobals(NIL); { sync to current port }
IF ewList^^.editWind = editWind THEN { is it the first window in list? }
BEGIN
h2 := ewList;
ewList := ewList^^.eNext;
END
ELSE
BEGIN
h := ewList;
WHILE h <> NIL DO
BEGIN
h2 := h^^.eNext;
IF h2^^.editWind = editWind THEN { found it }
BEGIN
h^^.eNext := h2^^.eNext;
h := NIL;
END;
IF h <> NIL THEN
h := h2;
END;
END;
DisposHandle(Handle(h2)); { get rid of information structure }
TEDispose(editTE); { toss text record }
DisposeWindow(editWind); { disposes of scroll bar, too }
editWind := NIL;
DoCursor(iBeamCursor);
END;
{ Blink the caret and make sure the cursor's an i-beam when it's}
{ in the non-scrollbar part of the window.}
PROCEDURE Idle;
BEGIN
SyncGlobals(NIL);
TEIdle(editTE); { blink that caret! }
DoCursor(iBeamCursor);
END;
{ ---------------------------------------------------------------- }
{ Internal File Routines }
{ ---------------------------------------------------------------- }
PROCEDURE ErrMesg (s : Str255);
VAR
ignore : integer;
BEGIN
ignore := FakeAlert(s, '', '', '', 1, 1, 'OK', '', '');
END;
{ Save the contents of the edit window. If there is no file bound}
{ to the window, ask for a file name. If askForFile is true, ask}
{ for a name even if the window is currently bound to a file. If}
{ bindToFile is true, bind the window to the file written to (if}
{ that's different than the currently bound file), and clear the}
{ window's dirty flag.}
{ Return true if the file was written without error. Return false}
{ if (a) user was asked for name and clicked Cancel (b) there was}
{ some error writing the file. In the latter case, the window is}
{ not bound to any new name given by user.}
{ Always returns false if the window isn't an edit window. This}
{ simplifies EWindowSave, EWindowSaveAs, EWindowSaveCopy. (They}
{ don't do the test.)}
FUNCTION SaveFile (theWind : WindowPtr;
askForFile : Boolean;
bindToFile : Boolean) : Boolean;
VAR
f : integer;
fndrInfo : FInfo; { finder info }
tmpFile : SFReply;
hText : Handle;
count : longint;
result, ignore : OSErr;
haveNewFile, breakflag : Boolean;
BEGIN
haveNewFile := false;
breakflag := false; { flag to detect a C 'return' statement }
IF NOT IsEWindow(theWind) THEN
BEGIN
SaveFile := false;
breakflag := true;
END
ELSE
BEGIN
SyncAllGlobals(theWind);
tmpFile := editFile;
IF (bound = false) OR askForFile THEN
BEGIN
SFPutFile(dlogWhere, 'Save File as:', editFile.fName, NIL, tmpFile);
IF NOT tmpFile.good THEN
BEGIN
SaveFile := false;
breakflag := true;
END
ELSE
BEGIN
haveNewFile := true;
IF GetFInfo(tmpFile.fName, tmpFile.vRefNum, fndrInfo) = noErr THEN { exists }
BEGIN
IF fndrInfo.fdType <> 'TEXT' THEN
BEGIN
ErrMesg('Not a TEXT File');
SaveFile := false;
breakflag := true;
END
END
ELSE { doesn't exist. create it. }
BEGIN
IF (Create(tmpFile.fName, tmpFile.vRefNum, creator, 'TEXT') <> noErr) THEN
BEGIN
ErrMesg('Can''t Create');
SaveFile := false;
breakflag := true;
END;
END;
END;
END;
END;
IF NOT breakflag THEN
BEGIN
IF FSOpen(tmpFile.fName, tmpFile.vRefNum, f) <> noErr THEN
ErrMesg('Can''t Open')
ELSE
BEGIN
DoCursor(watchCursor);
ignore := SetFPos(f, fsFromStart, longint(0));
hText := editTE^^.hText;
HLock(hText);
count := editTE^^.teLength;
result := FSWrite(f, count, hText^);
ignore := GetFPos(f, count);
ignore := SetEOF(f, count);
ignore := FSClose(f);
ignore := FlushVol(NIL, tmpFile.vRefNum);
HUnlock(hText);
DoCursor(iBeamCursor);
IF result = noerr THEN
BEGIN
IF bindToFile THEN
BEGIN
SetDirty(false);
IF haveNewFile THEN
BEGIN
SetWTitle(editWind, tmpFile.fName);
editInfo^^.bound := true;
editInfo^^.editFile := tmpFile;
END;
END;
SaveFile := true;
breakflag := true;
END
ELSE
ErrMesg('Write error!');
END;
IF NOT breakflag THEN
SaveFile := false;
END;
END;
{ Revert to version of file saved on disk. Doesn't check whether}
{ the window's really bound to a file or not, doesn't ask whether}
{ to really revert if the window's dirty, does no redrawing, etc.}
{ Just reports whether the file was read in successfully.}
FUNCTION Revert : Boolean;
VAR
result : Boolean;
f : integer;
len : longint;
h : Handle;
ignore : OSErr;
BEGIN
result := false;
DoCursor(watchCursor);
IF FSOpen(editFile.fName, editFile.vRefNum, f) <> noErr THEN
ErrMesg('Couldn''t open file')
ELSE
BEGIN
ignore := GetEOF(f, len);
IF len >= 32000 THEN
ErrMesg('File is too big')
ELSE
BEGIN
h := Handle(TEGetText(editTE));
SetHandleSize(h, len);
HLock(h);
ignore := FSRead(f, len, h^);
HUnlock(h);
editTE^^.teLength := len;
TESetSelect(longint(0), longint(0), editTE); { set caret at start }
result := true;
SetDirty(false);
END;
ignore := FSClose(f);
END;
DoCursor(iBeamCursor);
Revert := result;
END;
{ ------------------------------------------------------------ }
{ Lowest-level Interface (Public) Routines }
{ ------------------------------------------------------------ }
{}
{ Return true/false to indicate whether the window is really an}
{ edit window.}
FUNCTION IsEWindow;
BEGIN
IsEWindow := GetEInfo(theWind) <> NIL;
END;
{ Return true/false to indicate whether the text associated with}
{ the window has been changed since the last save/revert (or since}
{ created, if not bound to file).}
FUNCTION IsEWindowDirty;
VAR
eInfo : EIHandle;
BEGIN
eInfo := GetEInfo(theWind);
IF eInfo <> NIL THEN
IsEWindowDirty := eInfo^^.dirty
ELSE
IsEwindowDirty := false;
END;
{ Return a handle to the TextEdit record associated with the edit}
{ window, or nil if it's not an edit window}
FUNCTION GetEWindowTE;
VAR
eInfo : EIHandle;
BEGIN
eInfo := GetEInfo(theWind);
IF eInfo <> NIL THEN
GetEWindowTE := eInfo^^.editTE
ELSE
GetEWindowTE := NIL;
END;
{ Return true/false depending on whether the editor is bound to}
{ a file or not, and a copy of the file info in the second}
{ argument. Pass nil for fileInfo if only want the return status.}
{ Returns false if it's not an edit window.}
FUNCTION GetEWindowFile;
VAR
eInfo : EIHandle;
BEGIN
eInfo := GetEInfo(theWind);
IF eInfo <> NIL THEN
BEGIN
IF fileInfo <> NIL THEN
fileInfo^ := eInfo^^.editFile;
GetEWindowFile := eInfo^^.bound
END
ELSE
GetEWindowFile := false;
END;
{ ---------------------------------------------------------------- }
{ Interface Display Routines }
{ ---------------------------------------------------------------- }
{}
{ Install event notification procedures for an edit window.}
PROCEDURE SetEWindowProcs;
VAR
eInfo : EIHandle;
BEGIN
IF theWind = NIL THEN { reset window creation defaults }
BEGIN
e_key := pKey;
e_activate := pActivate;
e_close := pClose;
END
ELSE
BEGIN
eInfo := GetEInfo(theWind);
IF eInfo <> NIL THEN
BEGIN
eInfo^^.eKey := pKey;
eInfo^^.eActivate := pActivate;
eInfo^^.eClose := pClose;
END;
END;
END;
{ Change the text display characteristics of an edit window}
{ and redisplay it.}
{ Scroll to home position before overhauling, because although}
{ the overhaul sets the viewRect to display an integral number}
{ of lines, there's no guarantee that the destRect offset will}
{ also be integral except at home position. Clipping is set to}
{ an empty rect so the scroll doesn't show.}
PROCEDURE SetEWindowStyle;
VAR
savePort : GrafPtr;
f : FontInfo;
te : TEHandle;
r : Rect;
oldWrap : integer;
BEGIN
IF theWind = NIL THEN { reset window creation defaults }
BEGIN
e_font := font;
e_size := size;
e_wrap := wrap;
e_just := just;
END
ELSE IF IsEWindow(theWind) THEN
BEGIN
GetPort(savePort);
SyncGlobals(theWind); { sync and set port }
te := editTE;
ScrollToHome;
oldWrap := te^^.crOnly;
te^^.crOnly := wrap;
TESetJust(just, te); { set justification }
TextFont(font); { set the font and point size }
TextSize(size); { of text record }
GetFontInfo(f);
te^^.lineHeight := f.ascent + f.descent + f.leading;
te^^.fontAscent := f.ascent;
te^^.txFont := font;
te^^.txSize := size;
OverhaulDisplay(true, (oldWrap >= 0) OR (wrap >= 0));
SetPort(savePort);
END;
END;
{ Redo display. Does not save current port. This is used by hosts}
{ that mess with the text externally to TransEdit. The arguments}
{ determine whether the text is scrolled to show the line with the}
{ caret, whether the lineStarts are recalculated, and whether the}
{ text should be marked dirty or not.}
PROCEDURE EWindowOverhaul;
BEGIN
IF (IsEWindow(theWind)) THEN
BEGIN
SyncGlobals(theWind);
OverhaulDisplay(showCaret, recalc);
DrawControls(editWind);
SetDirty(dirty);
END;
END;
{ ---------------------------------------------------------------- }
{ Menu Interface Routine }
{ ---------------------------------------------------------------- }
{}
{ Do Edit menu selection. This is only valid if an edit}
{ window is frontmost.}
PROCEDURE EWindowEditOp;
VAR
ignore : integer;
BEGIN
IF IsEWindow(FrontWindow) THEN
BEGIN
SyncGlobals(FrontWindow);
CASE item OF
{ cut selection, put in TE Scrap, clear clipboard and put}
{ TE scrap in it}
cut :
BEGIN
TECut(editTE);
ignore := ZeroScrap;
ignore := TEToScrap;
END;
{ copy selection to TE Scrap, clear clipboard and put}
{ TE scrap in it}
copy :
BEGIN
TECopy(editTE);
ignore := ZeroScrap;
ignore := TEToScrap;
END;
{ get clipboard into TE scrap, put TE scrap into edit record}
paste :
BEGIN
ignore := TEFromScrap;
TEPaste(editTE);
END;
{ delete selection without putting into TE scrap or clipboard}
clear :
TEDelete(editTE);
OTHERWISE
END;
AdjustDisplay;
SetDirty(true);
END;
END;
{ ---------------------------------------------------------------- }
{ Interface File Routines }
{ ---------------------------------------------------------------- }
{}
{ Set file creator for any files created by TransEdit}
PROCEDURE SetEWindowCreator;
BEGIN
creator := creat;
END;
{ Save the contents of the given window}
FUNCTION EWindowSave;
BEGIN
EWindowSave := SaveFile(theWind, false, true); { window to save }
{ don't ask for file if have one }
{ bind to new file if one given }
END;
{ Save the contents of the given window under a new name}
{ and bind to that name.}
FUNCTION EWindowSaveAs;
BEGIN
EWindowSaveAs := SaveFile(theWind, true, true);{ window to save }
{ ask for file even if have one }
{ bind to new file if one given }
END;
{ Save the contents of the given window under a new name, but}
{ don't bind to the name.}
FUNCTION EWindowSaveCopy;
BEGIN
EWindowSaveCopy := SaveFile(theWind, true, false); { window to save }
{ ask for file even if have one }
{ don't bind to file }
END;
{ Close the window. If it's dirty and is either bound to a file}
{ or (if not bound) has some text in it, ask about saving it first,}
{ giving user option of saving changes, tossing them, or}
{ cancelling altogether.}
{ Return true if the file was saved and the window closed, false if}
{ user cancelled or there was an error.}
FUNCTION EWindowClose;
VAR
return : Boolean;
BEGIN
return := true;
IF IsEWindow(theWind) = true THEN
BEGIN
SyncAllGlobals(theWind);
IF ((bound OR (editTE^^.teLength > 0)) AND dirty) THEN
CASE (FakeAlert('Save changes to"', editFile.fName, '"?', '', 3, 3, 'Cancel', 'Discard', 'Save')) OF
1 : { cancel Close }
return := false;
2 :
; { toss changes }
3 :
IF SaveFile(editWind, false, false) = false THEN { window to save }
{ don't ask for name }
{ don't bind to name }
return := false; { cancelled or error - cancel Close }
OTHERWISE
END;
IF return THEN
SkelRmveWind(editWind);
EWindowClose := return;
END;
END;
{ Revert to saved version of file on disk. theWind must be an edit}
{ window, and must be bound to a file. Returns false if one of these}
{ conditions is not met, or if they are met but there was an error}
{ reading the file.}
{ The window need not be dirty, but if it is, the user is asked}
{ whether to really revert.}
FUNCTION EWindowRevert;
VAR
return : Boolean;
BEGIN
return := true;
IF NOT IsEWindow(theWind) THEN
return := false
ELSE
BEGIN
SyncAllGlobals(theWind);
IF NOT bound THEN { no file to revert to }
return := false
ELSE
BEGIN
IF dirty THEN
IF FakeAlert('"', editFile.fName, '" has been changed. Really revert?', '', 2, 1, 'Cancel', 'Revert', '') = 1 THEN
return := false;
END;
END;
IF return = true THEN
IF Revert = false THEN
return := false;
IF return = true THEN
BEGIN
ScrollToHome;
OverhaulDisplay(true, true);
DrawControls(editWind);
ValidRect(editWind^.portRect);
END;
EWindowRevert := return;
END;
{ ---------------------------------------------------------------- }
{ Interface Initialization/Termination Routines }
{ ---------------------------------------------------------------- }
{}
{ Initialize the window and associated data structures.}
{ Return window pointer or nil if some sort of error.}
{}
{ Preserves the current port.}
FUNCTION NewEWindow;
VAR
savePort : GrafPtr;
r : Rect;
mytype : SFTypeList;
s, s2 : Str255;
tPtr : STRING[64];
eInfo : EIHandle;
failure : Boolean;
BEGIN
mytype[0] := 'TEXT';
failure := false; {no failure yet!}
IF bindToFile THEN
{ If supposed to bind to file, ask for name. Return without doing}
{ anything if Cancel button clicked.}
BEGIN
SFGetFile(dlogWhere, '', NIL, 1, myType, NIL, editFile);
IF NOT editFile.good THEN
failure := true
END;
IF NOT failure THEN
BEGIN
bound := bindToFile;
IF bound THEN
{ Create window and install handler. Set window title: If window is}
{ to be bound to file, use name of file. Otherwise use any title that}
{ was passed in. If nil was passed, use a default name ("Untitled nnn").}
{ Also copy the name into the file info structure even if the window is}
{ unbound, because the Save operations expect to find it there as the}
{ most likely name to use if the window is untitled.}
{ Save and restore port, because it gets reset by the rest of the}
{ initialization code.}
tPtr := editFile.fName
ELSE
BEGIN
IF title <> '' THEN
tPtr := title
ELSE
BEGIN
windId := windID + 1; { Who's says C is easier? The C code for this }
NumToString(longint(windID), s2); { is ridiculous!!!!! }
tPtr := concat('Untitled ', s2);
END;
editFile.fName := tPtr;
END;
editWind := NewWindow(NIL, bounds, tPtr, false, documentProc, behind, goAway, refNum);
GetPort(savePort);
SkelWindow(editWind, @Mouse, @Key, @Update, @Activate, @Close, @Clobber, @Idle, true);
{ mouse click handler }
{ key click handler }
{ window updating procedure }
{ window activate/deactivate procedure }
{ window close procedure }
{ window disposal procedure }
{ idle proc }
{ idle only when frontmost }
{ Build the scroll bar.}
CalcScrollRect(r);
editScroll := NewControl(editWind, r, '', true, 0, 0, 0, scrollBarProc, longint(0));
{ Create the TE record used for text display. Use default}
{ characteristics.}
GetEditRect(r);
editTE := TENew(r, r);
SetClikLoop(@AutoScroll, editTE); { set autoscroll proc }
{ Get new information structure, attach to list of known edit}
{ windows.}
eInfo := EIHandle(NewHandle(Size(sizeof(EditInfoRec))));
editInfo := eInfo;
eInfo^^.eNext := ewList;
ewList := eInfo;
eInfo^^.editWind := editWind;
eInfo^^.scroll := editScroll;
eInfo^^.editTE := editTE;
eInfo^^.bound := bound;
eInfo^^.editFile := editFile;
{ Install default event notification procedures, font characteristics.}
SetEWindowProcs(editWind, e_key, e_activate, e_close);
SetEWindowStyle(editWind, e_font, e_size, e_wrap, e_just);
SetDirty(false);
{ If supposed to read file, do so. Check the return value of}
{ Revert and toss the window if there was an error.}
IF bindToFile THEN
IF (Revert = false) THEN
BEGIN
SkelRmveWind(editWind);
SetPort(savePort);
failure := true;
END;
END;
IF NOT failure THEN
BEGIN
{ Show window if specified as visible, and return a pointer to it.}
SyncGlobals(editWind);
OverhaulDisplay(true, true);
IF visible THEN
ShowWindow(editWind);
SetPort(savePort);
NewEWindow := editWind;
END
ELSE
NewEWindow := NIL;
END;
{ Look through the list of windows, shutting down all the edit}
{ windows. If any window is dirty, ask user about saving it first.}
{ If the user cancels on any such request, ClobberEWindows returns}
{ false. If all edit windows are shut down, return true. It is}
{ then safe for the host to exit.}
{ When a window *is* shut down, have to start looking through the}
{ window list again, since theWind no longer points anywhere}
{ meaningful.}
FUNCTION ClobberEWindows;
VAR
theWind : WindowPtr;
breakflag, flag2 : Boolean;
mypeek : WindowPeek;
BEGIN
breakflag := false;
WHILE NOT breakflag DO
BEGIN
theWind := FrontWindow;
flag2 := false;
WHILE (theWind <> NIL) AND NOT flag2 DO { all edit windows are not shut down }
BEGIN
IF ISEWindow(theWind) THEN
flag2 := true
ELSE
BEGIN
mypeek := WindowPeek(theWind);
theWind := WindowPtr(mypeek^.nextWindow);
END;
END;
IF theWind = NIL THEN
BEGIN
ClobberEWindows := true;
breakflag := true;
END
ELSE
BEGIN
IF theWind <> FrontWindow THEN
BEGIN
SelectWindow(theWind);
ShowWindow(theWind);
EWindowOverhaul(theWind, false, false, IsEWindowDirty(theWind));
SetPort(theWind);
ValidRect(theWind^.portRect);
END;
IF EWindowClose(theWind) = false THEN { cancel or error }
BEGIN
ClobberEWindows := false;
breakflag := true;
END;
END;
END;
END;
END.